﻿#define VERBOSE
using System;
using System.Collections.Generic;

using System.Linq;
using System.Runtime.CompilerServices;
using RayDen.Library.Components.SystemComponents;
using RayDen.Library.Core;
using RayDen.RayEngine.Core.Interface;
using RayDen.RayEngine.Core.Types;

namespace RayDen.RayEngine.Samplers
{
    public class AdaptiveBlockSampler : ISampler
    {

        public const double ErrorStop = 0.0002;
        public const double ErrorSplit = 256.0 * ErrorStop;

        public static int ImportantBlockSamples = 10;
        public static int NormalBlockSamples = 1;



        public static int WarmupPasses = 1;
        public static int SamplesPerPassX4 = 4;
        public static int MagicValue2 = 1;
        public static int SamplesPerPass = 1;
        public int Width { get; protected set; }
        public int Height { get; protected set; }

        public int screenWidth, screenHeight, screenStartLine;
        public int pass;
        public bool lowLatency;
        public int valuesPerSample;

        private IRandomProvider rnd;

        public int tIdx;
        protected int _currentSampleScreenY, currentBlockSample;
        internal AdaptiveImageFilm Film;


        public SampleBlock CurrentBlock;

        private List<SampleBlock> blocks, subBlocks;

        public AdaptiveBlockSampler()
        {
            rnd =
                new FastRandom();
            //new HaltonSequence();
            TotalSamples = 0L;
            blocks = new List<SampleBlock>(65535);
            subBlocks = new List<SampleBlock>(65535);
        }

        int ScreenHeight
        {
            get { return screenStartLine + screenHeight; }
        }

        private void EvalCurrentBlock()
        {
            this.CurrentBlock.BlockPass++;
        }



        FastRandom _rnd = new FastRandom();
        private int blockIndex = 0, generation;
        private bool warmUpcomplete = false;


        private void Classify(List<SampleBlock> sampleBlocks)
        {
            if (sampleBlocks.Count == 1)
            {
                sampleBlocks[0].Importance = 0.2f;
                return;
            }
            sampleBlocks.ForEach(item => item.CachedError = item.BlockError(Film));
            var maxError = sampleBlocks.Max(item => item.CachedError);
            var minError = sampleBlocks.Min(item => item.CachedError);

            foreach (var sampleBlock in sampleBlocks)
            {
                sampleBlock.Importance = ((sampleBlock.CachedError)/(maxError - minError));

            }
            sampleBlocks.Sort((a, b) => a.Importance.CompareTo(b.Importance));
            sampleBlocks.Reverse(); 
        }

        public Sample GetSample(Sample prevSample)
        {

            TotalSamples++;

            if (!warmUpcomplete)
            {
                var wmsample = new Sample(this);
                var rx = wmsample.GetLazyValue();
                var ry = wmsample.GetLazyValue();
                wmsample.imageX = rx * screenWidth;
                wmsample.imageY = ry * screenHeight;


                warmUpcomplete = TotalSamples >= screenWidth * screenHeight * WarmupPasses;

                return wmsample;
            }
#if VERBOSE
            if (TotalSamples == screenWidth * screenHeight * WarmupPasses+1)
            {
                Tracer.TraceLine("T{0} Warmup complete", tIdx);
                var sampleBlocks = new HashSet<SampleBlock>();
                foreach (var sampleBlock in blocks)
                {
                    if (!sampleBlocks.Contains(sampleBlock) && (sampleBlock.BlockError(Film) > ErrorStop))
                    {
                        sampleBlock.CachedError = sampleBlock.BlockError(Film);
                        sampleBlocks.Add(sampleBlock);
                    }
                }

                this.blocks = sampleBlocks.ToList();
                Tracer.TraceLine("T{0} duplicate removal complete", tIdx);

                Classify(blocks);
                Tracer.TraceLine("T{0} classification complete", tIdx);

            }
#endif
            CurrentBlock.TotalSamples++;


            if (blocks.Count == 0)
            {
                this.Init(Width, Height);
                Tracer.TraceLine("Adaptive sampling complete! Start another iteration");
                return null;
            }
            currentBlockSample++;

            if (currentBlockSample >= CurrentBlock.SamplesInBlock(ImportantBlockSamples))
            {
                blockIndex++;
                currentBlockSample= 0;
            }

            if (blockIndex >= blocks.Count)
            {
                blockIndex = Math.Min(tIdx, blocks.Count - 1);
            }
            CurrentBlock = blocks[blockIndex];
            if (CurrentBlock.TotalSamples >= CurrentBlock.SamplesPerIteration * CurrentBlock.BlockWidth * CurrentBlock.BlockHeight)
            {
                var blockError = CurrentBlock.BlockError(this.Film);

                if (blockError > ErrorSplit)
                {
                    this.SplitBlock(CurrentBlock);
                }
                else if (blockError < ErrorStop)
                {
#if VERBOSE
                    Tracer.TraceLine("{0} Block is ready", blockIndex);
                    Tracer.TraceLine("Rem block {0} block error {1} ", blocks[blockIndex].ToString(), blockError,
                                     blocks[blockIndex].BlockWidth, blocks[blockIndex].BlockHeight);
#endif
                    this.TotalSamples += this.CurrentBlock.SamplerPerBlock;
#if VERBOSE
                    Tracer.TraceLine("Block Count " + blocks.Count);
#endif
                }
                else if (subBlocks.Count < 65535)
                {

                    foreach (var block in blocks)
                    {
                        var proposalB = block.Split(block.SamplesPerIteration);
                        foreach (var proposalBlock in proposalB)
                        {
                            if (proposalBlock.BlockError(Film) > block.BlockError(Film))
                            {
                                //this.blocks.AddRange(proposalBlock.Split(CurrentBlock.SamplesPerIteration));
                                subBlocks.Add(proposalBlock);
                            }
                            else
                            {
                                //this.blocks.Add(proposalBlock);
                                //this.blocks.AddRange(proposalBlock.Split(proposalBlock.SamplesPerIteration * 2));
                                subBlocks.AddRange(proposalBlock.Split(proposalBlock.SamplesPerIteration));
                                //foreach (var block in blocks)
                                //{
                                //    Film.StatsRect(block.Xstart, block.Ystart, block.BlockWidth, block.BlockHeight, blockIndex);
                                //}
                            }
                        }
                    }
                }

#if VERBOSE
                //Tracer.TraceLine("T{4}- Removing {2}/{3} block {1} error {0}", blockError, CurrentBlock.ToString(), blocks.Count, blockIndex, tIdx);

#endif
                this.blocks.Remove(CurrentBlock);

                if (blocks.Count == 0 && this.subBlocks.Count > 0)
                {
                    Tracer.TraceLine("T{0} Dicing", tIdx);

                    generation++;
                    this.blocks = this.subBlocks.ToList();
                    this.subBlocks.Clear();

                    var sampleBlocks = new HashSet<SampleBlock>();
                    foreach (var sampleBlock in blocks)
                    {
                        if (!sampleBlocks.Contains(sampleBlock) && (sampleBlock.BlockError(Film) > ErrorStop))
                        {
                            sampleBlock.CachedError = sampleBlock.BlockError(Film);
                            sampleBlocks.Add(sampleBlock);
                        }
                    }


#if VERBOSE
                    Tracer.TraceLine("Removing {0} duplicates", this.blocks.Count - sampleBlocks.Count);

                    Tracer.TraceLine("T{0} Generation {1} sampling", tIdx, generation);



#endif
                   Classify(blocks);
                }

                if (blockIndex >= this.blocks.Count)
                {
                    this.blockIndex = 0;
                }

                this.CurrentBlock = this.blocks[blockIndex];
                this.EvalCurrentBlock();
                pass += CurrentBlock.SamplesPerIteration;


#if VERBOSE2
                for (int index = 0; index < blocks.Count; index++)
                {
                    var block = blocks[index];
                    //bool remove = false;
                    //if ((block.Xstart + block.BlockWidth) > screenWidth)
                    //{
                    //    Tracer.TraceLine("Invalid block Width " + (block.Xstart + block.BlockWidth));
                    //    remove = true;
                    //}
                    //if ((block.Ystart + block.BlockHeight) > screenHeight)
                    //{
                    //    Tracer.TraceLine("Invalid block Height " + (block.Ystart + block.BlockHeight));
                    //    remove = true;
                    //}
                    Film.StatsRect(block.Xstart, block.Ystart, block.BlockWidth, block.BlockHeight, blockIndex);
                    //if (remove)
                    //{
                    //    blocks.RemoveAt(index);
                    //    Tracer.TraceLine("Removing invalid block {0} block size {2}x{3}", blockIndex, blockError, blocks[blockIndex].BlockWidth, blocks[blockIndex].BlockHeight);

                    //}
                }

#endif
            }

            CurrentBlock = blocks[blockIndex];
            Sample sample = CurrentBlock.GetSample(this);

            return sample;
        }

        //public float Error(int x, int y)


        #region ISampler Members


        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public float GetLazyValue(Sample sample)
        {
            return rnd.NextFloat();
        }

        public float GetLDValue(Sample sample)
        {
            return rnd.NextFloat();

        }

        public void Init(int width, int height)
        {
            rnd.Reinitialise((int)DateTime.Now.Ticks);
            if (screenWidth == 0 && screenHeight == 0)
            {
                Width = screenWidth = width;
                Height = screenHeight = height;
                //screenWidth = width;
                //screenHeight = height;
            }
            screenStartLine = Math.Max(0, screenStartLine);
            _currentSampleScreenY = Math.Max(0, screenStartLine);

            pass = 0;
            TotalSamples = 0;
            blockIndex = tIdx;
            //var tx = 1;
            //var ty = 1;
            /*
                        var tWidth = Width / tx;
                        var tHeight = Height / ty;
                        for (int y = 0; y < Height-tHeight; y += tHeight)
                        {
                            for (int x = 0; x < Width-tWidth; x += tWidth)
                            {
                                var block = new Block() { BlockWidth = tWidth, BlockHeight = tHeight, SamplesPerIteration = SamplesPerPass, Xstart = x, Ystart = y };
                                blocks.Add(block);
                            }
                        }

                        CurrentBlock = blocks[0];*/

            CurrentBlock = new SampleBlock() { BlockWidth = screenWidth, BlockHeight = screenHeight, SamplesPerIteration = SamplesPerPass };
            //this.blocks.Add(CurrentBlock);
            this.blocks.AddRange(CurrentBlock.MassSplit(4, SamplesPerPass));

            //this.blocks.Sort((a, b) => a.CachedError.CompareTo(b.CachedError));
            //this.blocks.ForEach(item => { item.Important = this.blocks.IndexOf(item) > this.blocks.Count / 2; });
        }

        private void SplitBlock(SampleBlock blockToSplit)
        {
            var index = subBlocks.IndexOf(blockToSplit);

            if (((blockToSplit.BlockWidth / 2) <= 1) || (blockToSplit.BlockHeight / 2) <= 1)
            {
                subBlocks.Remove(blockToSplit);
                return;
            }

            this.subBlocks.InsertRange(Math.Max(0, index - 1), blockToSplit.Split(blockToSplit.SamplesPerIteration));

            subBlocks.Remove(blockToSplit);
            ///blocks.AddRange(blockToSplit.Split(blockToSplit.SamplesPerIteration));
        }

        public int GetPass()
        {
            if (!warmUpcomplete)
            {
                return (int)(TotalSamples / (screenHeight * screenWidth));
            }
            return pass;
        }



        public long TotalSamples { get; set; }
        public float GetLazyValue(Sample sample, int index)
        {
            return rnd.NextFloat();
        }


        #endregion

        public void Reinitialise(int seed)
        {
            this.rnd.Reinitialise(seed);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public float NextFloat()
        {
            return rnd.NextFloat();
        }
    }
}